package com.fourpigeon.service.impl;

import java.io.File;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.BiFunction;
import java.util.function.Function;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.Console;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.lang.func.Func;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.mail.Mail;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.fourpigeon.dao.*;
import com.fourpigeon.domain.*;
import com.fourpigeon.service.BlogService;
import com.fourpigeon.utils.IdGenerator;
import com.fourpigeon.utils.MailUtil;
import com.fourpigeon.utils.SystemConstants;
import com.fourpigeon.utils.UserHolder;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.security.RunAs;
import jodd.time.TimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import com.fourpigeon.service.UserService;

import static com.fourpigeon.utils.FormatUtils.isPhone;
import static com.fourpigeon.utils.IdGenerator.BEGIN_TIMESTAMP;
import static com.fourpigeon.utils.IdGenerator.BIT_COUNT;
import static com.fourpigeon.utils.RedisConstants.*;
import static com.fourpigeon.utils.SystemConstants.WX_APP_ID;
import static com.fourpigeon.utils.SystemConstants.WX_APP_SECRET;

@Slf4j
@Service
public class UserServiceImpl implements UserService{
    @Autowired
    private MailUtil mailUtil;
    @Autowired
    private UserDao userDao;
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private IdGenerator idGenerator;
    @Autowired
    private SyncTableDao syncTableDao;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private BlogAgreeDao blogAgreeDao;
    @Autowired
    private BlogDailyDao blogDailyDao;
    @Autowired
    private BlogPracticeDao blogPracticeDao;
    @Autowired
    private BlogDealDao blogDealDao;
    private Random random = new Random();

    @Override
    public User geyByPhone(String phone) {
        return userDao.getByPhone(phone);
    }

    private Map<Short, BiFunction<Long,Long,List<BlogAgree>>> agreeGetsMap = new HashMap<>();
    {
        agreeGetsMap.put((short) 0,(userId,startId) -> blogAgreeDao.getJuniorByUserId(userId,startId));
        agreeGetsMap.put((short) 1,(userId,startId) -> blogAgreeDao.getAdvancedByUserId(userId,startId));
        agreeGetsMap.put((short) 2,(userId,startId) -> blogAgreeDao.getSeniorByUserId(userId,startId));
    }

    /**
     * 用户名——密码登录方式，
     * 使用Redis存储token
     * @param loginDTO 前端数据
     * @return 结果信息
     */
    @Override
    public Result login(LoginDTO loginDTO) {
        return Result.ok();
    }

    @Override
    public Result loginNameWithPassWord(LoginDTO loginDTO) {
        //验证用户名
        String userName = loginDTO.getName();
        //验证密码
        String password = loginDTO.getPassword();
        User user = userDao.getByUserName(userName);
        //不一致，报错
        if (user == null) {
            return Result.fail("不存在该用户!");
        }
        //一致，生成token作为登录令牌
        String token = UUID.randomUUID().toString(true);
        //创建一个UserDTO的Bean
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        //将Bean转换成一个Map，转换方式是一个UserDTO的成员变量名称为field，值为field的值
        Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(), CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));//设置转换方法
        //保存token到Redis
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+token,map);
        //设置30min过期时间
        stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,30, TimeUnit.MINUTES);
        //返回token给客户端
        return Result.ok(token);
    }

    /**
     * 注册方法
     * @param registerDTO 前端数据
     * @return 结果信息
     */
    @Override
    public Result register(RegisterDTO registerDTO) {
        //查询用户名是否存在
        User temp = userDao.getByUserName(registerDTO.getName());
        //存在，返回错误
        if (temp != null) {
            return Result.fail("用户名已经存在!");
        }
        //不存在，创建用户
        User user = new User(registerDTO.getName(), registerDTO.getPassword());
        user.setCreateTime(LocalDateTime.now());
        //写入数据库
        if (!userDao.save(user)) {
            return Result.fail("服务器异常,请稍后再试!");
        }
        //返回结果
        return Result.ok("注册成功!");
    }

    /**
     * 手机号——验证码登录
     * @param loginDTO 前端数据
     * @return 结果信息
     */
    @Override
    public Result loginPhoneCode(LoginDTO loginDTO) {
        //检验手机号(格式、是否存在于数据库中)
        if (!isPhone(loginDTO.getPhone())) {
            return Result.fail("手机号格式错误!");
        }
        User user = geyByPhone(loginDTO.getPhone());
        //查询不到，手机号未注册
        if (user==null){
            user = createUserByPhone(loginDTO.getPhone());
            if (user == null) {
                return Result.fail("服务器异常,请稍后再试!");
            }
        }
        //Redis检验验证码
        String s = stringRedisTemplate.opsForValue().get(LOGIN_CODE + loginDTO.getPhone());
        //不存在，返回错误信息
        if (s == null || s == "") {
            return Result.fail("验证码错误!");
        }
        //存在，删除验证码并生成token
        //TODO 删除验证码
        stringRedisTemplate.expire(LOGIN_CODE+loginDTO.getPhone(),1,TimeUnit.SECONDS);
        String token = UUID.randomUUID().toString(true);
        //保存token到Redis
        //TODO beanToMap方法对NULL的处理
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor(
                        (fieldName,fieldValue)->{
                            if(fieldValue!=null) return fieldValue.toString();
                            return " ";
                        })
                );
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+token,map);
        stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+token,30,TimeUnit.MINUTES);
        //返回token
        return Result.ok(token);
    }

    /**
     * 使用手机号创建用户
     * @param phone 信息
     * @return 结果信息
     */
    private User createUserByPhone(String phone) {
        User user = new User();
        //设置手机号
        user.setPhone(phone);
        //随机生成用户名
        user.setName("user_"+RandomUtil.randomString(10)+phone);
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(null);
        if (!userDao.save(user)) {
            return null;
        }
        return user;
    }

    /**
     * 发送验证码
     * @param phone 手机号
     * @return 结果信息
     */
    @Override
    public Result sendCode(String phone) {
        String code = RandomUtil.randomNumbers(6);
        stringRedisTemplate.opsForValue().set(LOGIN_CODE+phone,code);
        stringRedisTemplate.expire(LOGIN_CODE+phone,5,TimeUnit.MINUTES);
        Console.log("验证码发送成功: "+code);
        return Result.ok("发送成功!");
    }

    /**
     * 获取当前用户信息
     * @return 结果信息
     */
    @Override
    public Result getMe() {
        Long userId = UserHolder.getUser().getId();
        //查询数据库获取全部信息
        User user = userDao.getById(userId);
        if (user == null) {
            return Result.fail("用户不存在!");
        }
        user.setPassword("***********");
        UserInfo userInfo = BeanUtil.copyProperties(user, UserInfo.class);
        //处理头像链接
        userInfo.handlerImgs();
        return Result.ok(userInfo);
    }

    /**
     * 获取指定用户信息
     * @param userId 用户id
     * @return 结果信息
     */
    @Override
    public Result getOther(Long userId) {
        User user = userDao.getById(userId);
        user.setPassword("");
        UserInfo userInfo = BeanUtil.copyProperties(user, UserInfo.class);
        //处理头像链接
        userInfo.handlerImgs();
        return Result.ok(userInfo);
    }

    //代理对象
    private UserService proxy;
    //阻塞队列
    private static final BlockingQueue<Boolean> daoTasks = new ArrayBlockingQueue<>(1024);
    //sql操作列表
    private static final List<Function<Long,Boolean>> sqlList = new ArrayList<>();
    {
        sqlList.add((id)->syncTableDao.updateJunior(id));
        sqlList.add((id)->syncTableDao.updateAdvanced(id));
        sqlList.add((id)->syncTableDao.updateSenior(id));
        sqlList.add((id)->syncTableDao.updateDaily(id));
        sqlList.add((id)->syncTableDao.updateDeal(id));
        sqlList.add((id)->syncTableDao.updatePractice(id));
    }
    //队列
    private static final Queue<Long> ids = new ArrayDeque<>();
    //操作阻塞队列的线程池
    private static final ExecutorService DAO_EXECUTORS = Executors.newSingleThreadExecutor();
    //为线程提供需要执行的操作
    @PostConstruct
    private void init(){
        DAO_EXECUTORS.submit(new DaoHandler());
    }

    /**
     * 操作阻塞队列的线程的执行逻辑
     */
    private static class DaoHandler implements Runnable{
        @Override
        public void run() {
            while(true){
                try{
                    //获取阻塞队列中的信息
                    Boolean take = daoTasks.take();
                    Long id = ids.poll();
                    //执行关联更新SQL
                    for (Function<Long, Boolean> func : sqlList) {
                        Boolean isSuccess = func.apply(id);
                        System.out.println();
                        log.info("id = "+id);
                        log.info("hand out: "+isSuccess);
                    }
                }catch (Exception e){
                    log.error("处理SQL语句异常",e);
                }
            }
        }
    }

    /**
     * 修改个人信息
     * @return 结果信息
     */
    @Override
    public Result updateInfo(UserInfo userInfo) {
        //TODO 获取用户id
        UserDTO userDTO = UserHolder.getUser();
        User user = userDao.getById(userDTO.getId());
        user = user.matchAndUpdate(userInfo);
        if (user == null)
            return Result.ok("更新成功!");
        //更新数据库
        userDao.updateUserInfo(user);
        //加入阻塞队列，执行关联数据库字段更新
        ids.add(UserHolder.getUser().getId());
        daoTasks.add(true);
        proxy = (UserService) AopContext.currentProxy();
        return Result.ok("更新成功!");
    }

    /**
     * 微信授权登录
     * @param code 微信登录凭证
     * @return 结果信息
     */
    @Override
    public Result loginWechat(String code,String userName) {
        System.out.println("code="+code);
        //发送请求给微信服务器的对应接口
        //通过code和其他信息换取用户id
        String url = "https://api.weixin.qq.com/sns/jscode2session?appid="+WX_APP_ID+"&secret="+WX_APP_SECRET+"&js_code="+code+"&grant_type=authorization_code";
        String json = HttpUtil.get(url);
        System.out.println("json="+json);
        //解析微信响应数据(json)
        WechatLogin bean = JSONUtil.toBean(json, WechatLogin.class, true);
        if (bean.getErrcode() != null) {
            //处理错误码
            switch (bean.getErrcode()) {
                case 40029 -> {
                    return Result.fail("code无效!");
                }
                case 45011 -> {
                    return Result.fail("访问过于频繁，请稍后再试!");
                }
                case 40226 -> {
                    return Result.fail("高风险等级用户!");
                }
                case -1 -> {
                    return Result.fail("系统繁忙，请稍后再试!");
                }
            }
        }
        //查询Redis，查看是否首次登录
        String id = stringRedisTemplate.opsForValue().get(OPENID_ID_KEY + bean.getOpenid());
        //首次登录，生成本地服务器用户id
        if (StrUtil.isBlank(id)) {
            //绑定微信用户id和本地服务器用户id
            Long tempId = idGenerator.nextUserId();
            User user = new User(tempId,
                    bean.getOpenid(),
                    "user_"+RandomUtil.randomString(10));

            //只同步第一次的方案
            //如果userName不为空，更改用户名称
            Optional.ofNullable(userName).ifPresent(user::setName);

            //存储数据库
            userDao.save(user);
            //存储Redis
            stringRedisTemplate.opsForValue().set(OPENID_ID_KEY+bean.getOpenid(),user.getId().toString());
            id = tempId.toString();
        }
        long userId = Long.parseLong(id);
        //获取user对象
        User user = userDao.getById(userId);

        //生成业务登录凭证SessionId(token)
        String SessionId = UUID.randomUUID().toString(true);
        //转换类型
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
        //转换成map
        Map<String, Object> userMap = BeanUtil.beanToMap(userDTO,
                new HashMap<>(),
                CopyOptions.create().setIgnoreNullValue(true)
                        .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        //存储Redis，设置过期时间
        stringRedisTemplate.opsForHash().putAll(LOGIN_USER_TOKEN_KEY+SessionId,userMap);
        stringRedisTemplate.expire(LOGIN_USER_TOKEN_KEY+SessionId,10060+random.nextInt(20),TimeUnit.MINUTES);
        //返回登录凭证(token)给前端
        return Result.ok(SessionId);
    }

    /**
     * 发送问题反馈邮件
     * @param issue 实体类
     * @return 结果
     */
    @Override
    public Result sendIssue(Issue issue) {
        if(issue.getFiles().equals("")){
            mailUtil.sendSimpleMail(issue.getTitle(),issue.getContext());
            return Result.ok();
        }
        String[] filesStr = StrUtil.splitToArray(issue.getFiles(), ',');
        List<File> files = new ArrayList<>();
        for (String s : filesStr) {
            files.add(new File(s));
        }
        mailUtil.sendAttachFileMail(issue.getTitle(),issue.getContext(),files);
        return Result.ok();
    }

    /**
     * 登出，移除token记录
     * @param authorization token
     * @return 结果
     */
    @Override
    public Result logOut(String authorization) {
        System.out.println("authorization:"+authorization);
        stringRedisTemplate.delete(LOGIN_USER_TOKEN_KEY+authorization);
        UserHolder.removeUser();
        return Result.ok();
    }

    /**
     * 分页获取当前用户发布过的日常贴
     * @param startId 起始id
     * @return 结果
     */
    @Override
    public Result getMyDailyPage(Long startId) {
        Long userId = UserHolder.getUser().getId();
        startId = zeroHandler(startId);
        List<BlogDaily> list = blogDailyDao.getByUserId(userId,startId);
        //查询是否点赞
        for (BlogDaily blogDaily : list) {
            blogDaily.setLike(likeOrNot(blogDaily.getId()));
            //处理贴子图片临时链接
            blogDaily.handlerImgs();
        }
        return Result.ok(list,(long)list.size());
    }

    /**
     * 判断日常贴是否被当前用户点赞
     * @param blogId 博客id
     * @return 结果信息
     */
    public Boolean likeOrNot(Long blogId) {
        Long userId = UserHolder.getUser().getId();
        Double score = stringRedisTemplate.opsForZSet().score(BLOG_LIKE_KEY + blogId, userId.toString());
        return score != null;
    }


    /**
     * 分页获取当前用户发布过的交易贴
     * @param startId 起始id
     * @return 结果
     */
    @Override
    public Result getMyDealPage(Long startId) {
        Long userId = UserHolder.getUser().getId();
        startId = zeroHandler(startId);
        List<BlogDeal> list = blogDealDao.getByUserId(userId,startId);
        for (BlogDeal blogDeal : list) {
            //处理贴子图片临时链接
            blogDeal.handlerImgs();
        }
        return Result.ok(list,(long)list.size());
    }

    /**
     * 分页获取当前用户发布过的约球贴
     * @param startId 起始id
     * @return 结果
     */
    @Override
    public Result getMyAgreePage(Short blogType, Long startId) {
        Long userId = UserHolder.getUser().getId();
        startId = zeroHandler(startId);
        List<BlogAgree> list = agreeGetsMap.get(blogType).apply(userId, startId);
        for (BlogAgree blogAgree : list) {
            //处理贴子图片临时链接
            blogAgree.handlerImgs();
        }
        return Result.ok(list,(long)list.size());
    }

    /**
     * 分页获取当前用户发布过的陪教贴
     * @param startId 起始id
     * @return 结果
     */
    @Override
    public Result getMyPracticePage(Long startId) {
        Long userId = UserHolder.getUser().getId();
        startId = zeroHandler(startId);
        List<BlogPractice> list = blogPracticeDao.getByUserId(userId,startId);
        for (BlogPractice blogPractice : list) {
            //处理贴子图片临时链接
            blogPractice.handlerImgs();
        }
        return Result.ok(list,(long)list.size());
    }

    private Long zeroHandler(Long startId) {
        if (startId != 0) return startId;
        startId = (Long) (LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) - BEGIN_TIMESTAMP) + 10086L;
        startId = startId << BIT_COUNT;
        System.out.println("startId: "+startId);
        return startId;
    }
}
